using System.Collections.Generic;
using System.Text.RegularExpressions;
using Furesoft.Papi.Tokenizer.Basic;

namespace Furesoft.Papi.Tokenizer.Basic
{
    public class BasicTokenParser
    {
        private readonly Dictionary<Tokens, string> _tokens;
        private readonly Dictionary<Tokens, MatchCollection> _regExMatchCollection;
        private string _inputString;
        private int _index;

        /// <summary>
        /// Tokens is an enumeration of all possible token values.
        /// </summary>
        public enum Tokens
        {
            UNDEFINED = 0,
            LET = 1,
            PRINT = 2,
            CLS = 3,
            REM = 4,
            END = 5,
            GOTO = 6,
            FOR = 7,
            STEP = 8,
            NEXT = 9,
            TO = 10,
            COLON = 11,
            EQUALS = 12,
            STRING = 13,
            IDENTIFIER = 14,
            WHITESPACE = 15,
            NEWLINE = 16,
            FLOAT = 17,
            INTEGER = 18,
            APOSTROPHE = 19,
            LPAREN = 20,
            RPAREN = 21,
            ASTERISK = 22,
            SLASH = 23,
            PLUS = 24,
            MINUS = 25
        }

        /// <summary>
        /// InputString Property
        /// </summary>
        /// <value>
        /// The string value that holds the input string.
        /// </value>
        public string InputString
        {
            set
            {
                _inputString = value;
                PrepareRegex();
            }
        }

        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <remarks>
        /// The constructor initalizes memory and adds all of the tokens to the token dictionary.
        /// </remarks>
        public BasicTokenParser()
        {
            _tokens = new Dictionary<Tokens, string>();
            _regExMatchCollection = new Dictionary<Tokens, MatchCollection>();
            _index = 0;
            _inputString = string.Empty;

            _tokens.Add(Tokens.LET, "[Ll][Ee][Tt]");
            _tokens.Add(Tokens.PRINT, "[Pp][Rr][Ii][Nn][Tt]");
            _tokens.Add(Tokens.CLS, "[Cc][Ll][Ss]");
            _tokens.Add(Tokens.REM, "[Rr][Ee][Mm][^\\r\\n]*");
            _tokens.Add(Tokens.END, "[Ee][Nn][Dd]");
            _tokens.Add(Tokens.GOTO, "[Gg][Oo][Tt][Oo]");
            _tokens.Add(Tokens.FOR, "[Ff][Oo][Rr]");
            _tokens.Add(Tokens.STEP, "[Ss][Tt][Ee][Pp]");
            _tokens.Add(Tokens.NEXT, "[Nn][Ee][Xx][Tt]");
            _tokens.Add(Tokens.TO, "[Tt][Oo]");
            _tokens.Add(Tokens.COLON, "\\:");
            _tokens.Add(Tokens.EQUALS, "=");
            _tokens.Add(Tokens.STRING, "\".*?\"");
            _tokens.Add(Tokens.IDENTIFIER, "[a-zA-Z_][a-zA-Z0-9_]*");
            _tokens.Add(Tokens.WHITESPACE, "[ \\t]+");
            _tokens.Add(Tokens.NEWLINE, "[\\r\\n]+");
            _tokens.Add(Tokens.FLOAT, "[0-9]?\\.+[0-9]+");
            _tokens.Add(Tokens.INTEGER, "[0-9]+");
            _tokens.Add(Tokens.APOSTROPHE, "'.*");
            _tokens.Add(Tokens.LPAREN, "\\(");
            _tokens.Add(Tokens.RPAREN, "\\)");
            _tokens.Add(Tokens.ASTERISK, "\\*");
            _tokens.Add(Tokens.SLASH, "\\/");
            _tokens.Add(Tokens.PLUS, "\\+");
            _tokens.Add(Tokens.MINUS, "\\-");
        }

        /// <summary>
        /// PrepareRegex prepares the regex for parsing by pre-matching the Regex tokens.
        /// </summary>
        private void PrepareRegex()
        {
            _regExMatchCollection.Clear();
            foreach (KeyValuePair<Tokens, string> pair in _tokens)
            {
                _regExMatchCollection.Add(pair.Key, Regex.Matches(_inputString, pair.Value));
            }
        }

        /// <summary>
        /// ResetParser resets the parser to its inital state. Reloading InputString is required.
        /// </summary>
        /// <seealso cref="InputString">
        public void ResetParser()
        {
            _index = 0;
            _inputString = string.Empty;
            _regExMatchCollection.Clear();
        }

        /// <summary>
        /// GetToken gets the next token in queue
        /// </summary>
        /// <remarks>
        /// GetToken attempts to the match the next character(s) using the
        /// Regex rules defined in the dictionary. If a match can not be
        /// located, then an Undefined token will be created with an empty
        /// string value. In addition, the token pointer will be incremented
        /// by one so that this token doesn't attempt to get identified again by
        /// GetToken()
        /// </remarks>
        public BasicToken GetToken()
        {
            if (_index >= _inputString.Length)
                return null;

            foreach (KeyValuePair<Tokens, MatchCollection> pair in _regExMatchCollection)
            {
                foreach (Match match in pair.Value)
                {
                    if (match.Index == _index)
                    {
                        _index += match.Length;
                        return new BasicToken(pair.Key, match.Value);
                    }

                    if (match.Index > _index)
                    {
                        break;
                    }
                }
            }
            _index++;
            return new BasicToken(Tokens.UNDEFINED, string.Empty);
        }

        /// <summary>
        /// Returns the next token that GetToken() will return.
        /// </summary>
        /// <seealso cref="Peek(PeekToken)">
        public PeekToken Peek()
        {
            return Peek(new PeekToken(_index, new BasicToken(Tokens.UNDEFINED, string.Empty)));
        }

        /// <summary>
        /// Returns the next token after the Token passed here
        /// </summary>
        /// <param name="peekToken">The PeekToken token returned from a previous Peek() call</param>
        /// <seealso cref="Peek()">
        public PeekToken Peek(PeekToken peekToken)
        {
            int oldIndex = _index;

            _index = peekToken.TokenIndex;

            if (_index >= _inputString.Length)
            {
                _index = oldIndex;
                return null;
            }

            foreach (KeyValuePair<Tokens, string> pair in _tokens)
            {
                Regex r = new Regex(pair.Value);
                Match m = r.Match(_inputString, _index);

                if (m.Success && m.Index == _index)
                {
                    _index += m.Length;
                    PeekToken pt = new PeekToken(_index, new BasicToken(pair.Key, m.Value));
                    _index = oldIndex;
                    return pt;
                }
            }
            PeekToken pt2 = new PeekToken(_index + 1, new BasicToken(Tokens.UNDEFINED, string.Empty));
            _index = oldIndex;
            return pt2;
        }
    }
}